/*
 ------------------------------------------------------------------------------
 Copyright (C) 2009-2011 Eternal Games.

 This file is part of the Eternal Tech source code.

 The Eternal Tech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Eternal Tech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Eternal Tech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// common.cpp - Commonly used systems in the engine
//


#include "../qcommon/qcommon.h"


#define MAX_NUM_ARGVS					64

static int					com_rdTarget;
static char					*com_rdBuffer;
static int					com_rdBufferSize;
static void					(*com_rdFlush)(int target, char *buffer);

static FILE					*com_logFileHandle;

static int					com_argc;
static char					*com_argv[MAX_NUM_ARGVS];

static jmp_buf				com_abortFrame;

bool						com_initialized = false;

int							com_frameTime = 0;

memoryPool_t				*com_cvarMemoryPool;
memoryPool_t				*com_cmdMemoryPool;
memoryPool_t				*com_parserMemoryPool;
memoryPool_t				*com_fileMemoryPool;
memoryPool_t				*com_genericMemoryPool;

cvar_t						*com_dedicated;
cvar_t						*com_developer;
cvar_t						*com_logFile;


/*
 ==============================================================================

    SERVER REDIRECTION

 ==============================================================================
*/


/*
 ==================
 Com_BeginRedirect
 ==================
*/
void Com_BeginRedirect (int target, char *buffer, int bufferSize, void (*flush)(int target, char *buffer))
{
	if (!target || !buffer || !bufferSize || !flush)
		return;

	com_rdTarget = target;
	com_rdBuffer = buffer;
	com_rdBufferSize = bufferSize;
	com_rdFlush = flush;

	*com_rdBuffer = 0;
}

/*
 ==================
 Com_EndRedirect
 ==================
*/
void Com_EndRedirect ()
{
	com_rdFlush(com_rdTarget, com_rdBuffer);

	com_rdTarget = 0;
	com_rdBuffer = NULL;
	com_rdBufferSize = 0;
	com_rdFlush = NULL;
}

/*
 ==================
 Com_Redirect
 ==================
*/
static void Com_Redirect (const char *msg)
{
	if (!com_rdTarget)
		return;

	if ((Str_Length(com_rdBuffer) + Str_Length(msg)) > (com_rdBufferSize - 1))
	{
		com_rdFlush(com_rdTarget, com_rdBuffer);
		*com_rdBuffer = 0;
	}

	Str_Copy(com_rdBuffer, msg, com_rdBufferSize);
}


/*
 ==============================================================================

    LOG FILE

 ==============================================================================
*/


/*
 ==================
 Com_OpenLogFile
 ==================
*/
static void Com_OpenLogFile ()
{
	time_t		t;
	struct tm   *lt;

	if (com_logFileHandle)
		return;

	if (com_logFile->integerValue == 1 || com_logFile->integerValue == 2)
		com_logFileHandle = fopen("consoleLog.txt", "wt");
	else if (com_logFile->integerValue == 3 || com_logFile->integerValue == 4)
		com_logFileHandle = fopen("consoleLog.txt", "at");

	if (!com_logFileHandle)
		return;

	if (com_logFile->integerValue == 2 || com_logFile->integerValue == 4)
		setvbuf(com_logFileHandle, NULL, _IONBF, 0);

	time(&t);
	lt = localtime(&t);

	fprintf(com_logFileHandle, "%s %s (%s %s)", ENGINE_VERSION, BUILD_STRING, __DATE__, __TIME__);
	fprintf(com_logFileHandle, "\nLog file opened on %s\n\n", asctime(lt));
}

/*
 ==================
 Com_CloseLogFile
 ==================
*/
static void Com_CloseLogFile ()
{
	time_t		t;
	struct tm	*lt;

	if (!com_logFileHandle)
		return;

	time(&t);
	lt = localtime(&t);

	fprintf(com_logFileHandle, "\nLog file closed on %s\n\n", asctime(lt));

	fclose(com_logFileHandle);
	com_logFileHandle = NULL;
}

/*
 ==================
 Com_LogPrint
 ==================
*/
static void Com_LogPrint (const char *text)
{
	if (!com_logFileHandle)
		return;

	fprintf(com_logFileHandle, "%s", text);

	if (com_logFile->integerValue == 2 || com_logFile->integerValue == 4)
		fflush(com_logFileHandle);   // Force it to save every time
}


/*
 ==============================================================================

    PRINT FUNCTIONS

 ==============================================================================
*/


/*
 ==================
 Com_Printf
 ==================
*/
void Com_Printf (const char *fmt, ...)
{
	char	string[MAX_PRINT_MESSAGE];
	va_list	argPtr;

	// Get the message
	va_start(argPtr, fmt);
	Str_VSPrintf(string, sizeof(string), fmt, argPtr);
	va_end(argPtr);

	// Redirect if needed
	if (com_rdTarget)
	{
		Com_Redirect(string);
		return;
	}

	// Print to client console
	Con_Print(string);

	// Also echo the same text to the dedicated console
	Win_Printf(string);

	// Log file
	if (com_logFile)
	{
		if (com_logFile->integerValue)
			Com_OpenLogFile();
		else
			Com_CloseLogFile();

		Com_LogPrint(string);
	}
}

/*
 ==================
 Com_DevPrintf
 ==================
*/
void Com_DevPrintf (const char *fmt, ...)
{
	char	string[MAX_PRINT_MESSAGE];
	va_list	argPtr;

	if (!com_developer || !com_developer->integerValue)
		return;

	// Get the message
	va_start(argPtr, fmt);
	Str_VSPrintf(string, sizeof(string), fmt, argPtr);
	va_end(argPtr);

	Com_Printf("%s", string);
}

/*
 ==================
 Com_Error
 ==================
*/
void Com_Error (int code, const char *fmt, ...)
{
	static bool   recursive;
	static char   string[MAX_PRINT_MESSAGE];
	static int	  count, lastTime;
	int			  time;
	va_list		  argPtr;

	// If we are getting a stream of errors, do an ERR_FATAL
	time = Sys_Milliseconds();

	if (time - lastTime < 100)
	{
		if (count >= 3)
			code = ERR_FATAL;

		count++;
	}
	else
		count = 0;

	lastTime = time;

	// Avoid recursion
	if (recursive)
		Win_Error("Com_Error: Recursive error after: %s", string);
	recursive = true;

	// Get the message
	va_start(argPtr, fmt);
	Str_VSPrintf(string, sizeof(string), fmt, argPtr);
	va_end(argPtr);

	// Handle the error
	if (code == ERR_DISCONNECT)
	{
		Com_Printf("*****************************\n");
		Com_Printf("ERROR: %s\n", string);
		Com_Printf("*****************************\n");

		CL_Drop();

		recursive = false;
		longjmp(com_abortFrame, -1);
	}
	else if (code == ERR_DROP)
	{
		Com_Printf("*****************************\n");
		Com_Printf("ERROR: %s\n", string);
		Com_Printf("*****************************\n");

		SV_Shutdown(va("Server crashed: %s\n", string), false);

		CL_Drop();

		recursive = false;
		longjmp(com_abortFrame, -1);
	}

	// ERR_FATAL
	SV_Shutdown(va("Server fatal crashed: %s\n", string), false);

	Win_Error("%s", string);
}


/*
 ==============================================================================

    CONFIG FILE

 ==============================================================================
*/


/*
 ==================
 Com_WriteConfigToFile
 ==================
*/
static void Com_WriteConfigToFile (const char *name)
{
	fileHandle_t	f;

	// If not initialized, make sure we don't write out anything
	if (!com_initialized)
		return;

	// Write key bindings and archive variables to the given config file
	FS_OpenFile(name, FS_WRITE, &f);
	if (!f)
	{
		Com_Printf("Couldn't write %s\n", name);
		return;
	}

	FS_Printf(f, "// Generated by " ENGINE_NAME ", modify at your own risk!" NEWLINE NEWLINE);

	Key_WriteBindings(f);
	CVar_WriteVariables(f);

	FS_CloseFile(f);
}


/*
 ==============================================================================

    CONSOLE COMMANDS

 ==============================================================================
*/


/*
 ==================
 Com_ShowConsole_f
 ==================
*/
static void Com_ShowConsole_f ()
{
	Win_ShowConsole(true);
}

/*
 ==================
 Com_Quit_f
 ==================
*/
static void Com_Quit_f ()
{
	Win_Exit();
}


/*
 ==============================================================================

    COMMAND LINE PARSING

 ==============================================================================
*/


/*
 ==================
 Com_AddEarlyCommands
 ==================
*/
static void Com_AddEarlyCommands (bool clear)
{
	int		i;

	for (i = 1; i < com_argc; i++)
	{

		if (Str_ICompare(com_argv[i], "+set"))
			continue;

		Cbuf_AddText(Str_VarArgs("set %s %s\n", com_argv[i+1], com_argv[i+2]));

		if (clear)
		{
			com_argv[i+0] = "";
			com_argv[i+1] = "";
			com_argv[i+2] = "";
		}

		i += 2;
	}
}

/*
 ==================
 Com_AddLateCommands
 ==================
*/
static bool Com_AddLateCommands ()
{
	char	text[1024];
	bool	ret = false;
	int		i, j;

	for (i = 1; i < com_argc; )
	{
		if (com_argv[i][0] != '+')
		{
			i++;
			continue;
		}

		for (j = 1; com_argv[i][j]; j++)
		{
			if (com_argv[i][j] != '+')
				break;
		}

		Str_Copy(text, com_argv[i]+j, sizeof(text));
		Str_Append(text, " ", sizeof(text));
		i++;

		while (i < com_argc){
			if (com_argv[i][0] == '+')
				break;

			Str_Append(text, com_argv[i], sizeof(text));
			Str_Append(text, " ", sizeof(text));
			i++;
		}

		if (text[0])
		{
			ret = true;

			text[strlen(text)-1] = '\n';
			Cbuf_AddText(text);
		}
	}

	return ret;
}

/*
 ==================
 Com_ParseCommandLine
 ==================
*/
static void Com_ParseCommandLine (char *cmdLine)
{
	com_argv[0] = "exe";
	com_argc = 1;

	while (*cmdLine){
		while (*cmdLine && ((*cmdLine <= 32) || (*cmdLine > 126)))
			cmdLine++;

		if (*cmdLine)
		{
			if (com_argc == MAX_NUM_ARGVS)
				Com_Error(ERR_FATAL, "Com_ParseCommandLine: MAX_NUM_ARGVS");

			com_argv[com_argc++] = cmdLine;

			while (*cmdLine && ((*cmdLine > 32) && (*cmdLine <= 126)))
				cmdLine++;

			if (*cmdLine)
			{
				*cmdLine = 0;
				cmdLine++;
			}
		}
	}
}


/*
 ==============================================================================

    FRAMEWORK

 ==============================================================================
*/


/*
 ==================
 Com_Framework
 ==================
*/
void Com_Framework ()
{
	static int   lastTime;
	int			 msec, minMsec = 1;

	if (setjmp(com_abortFrame))
		return;   // An error occurred, exit the entire frame

	// Write config file if needed
	if (CVar_GetModifiedFlags() & CVAR_ARCHIVE)
	{
		CVar_ClearModifiedFlags(CVAR_ARCHIVE);

		Com_WriteConfigToFile(CONFIG_FILE);
	}

	// TODO: Switch to or from dedicated server mode

	// Main event loop
	do {
		com_frameTime = Win_GetEvents();
		if (lastTime > com_frameTime)
			lastTime = com_frameTime;

		msec = com_frameTime - lastTime;
	} while (msec < minMsec);

	lastTime = com_frameTime;

	if (msec < 1)
		msec = 1;

	// Process commands
	Cbuf_Execute();

	// Run server and client frames
	SV_Frame (msec);
	CL_Frame (msec);
}


/*
 ==============================================================================

    INITIALIZATION AND SHUTDOWN

 ==============================================================================
*/


/*
 ==================
 Com_Init
 ==================
*/
void Com_Init (char *cmdLine)
{
	if (setjmp(com_abortFrame))
		Win_Error("Com_Init: Error during initialization");

	// Create memory pools
	com_cvarMemoryPool = Mem_RegisterPool("Common: Cvar system");
	com_cmdMemoryPool = Mem_RegisterPool("Common: Cmd system");
	com_parserMemoryPool = Mem_RegisterPool("Common: Parser system");
	com_fileMemoryPool = Mem_RegisterPool("Common: File system");
	com_genericMemoryPool = Mem_RegisterPool("Common: Generic system");

	// Parse the command line
	Com_ParseCommandLine(cmdLine);

	// Initialize the memory manager
	Mem_Init();

	// Initialize system module
	Win_InitSystem();

	// Initialize the subsystems that handle commands and variables
	Cbuf_Init();

	Cmd_Init();
	Cvar_Init();   // FIXME: Remove this later
	CVar_Init();

	Key_Init();

	// We need to add the early commands twice, because some file system
	// variables need to be set before execing config files, but we want
	// other parms to override the settings of the config files
	Com_AddEarlyCommands(false);
	Cbuf_Execute();

	// Initialize file system
	FS_InitFilesystem();
	FS_Init();

	Com_AddEarlyCommands(true);
	Cbuf_Execute();

	// Register variables
	com_dedicated = CVar_Register("com_dedicated", "0", CVAR_BOOL, CVAR_SERVERINFO, "Dedicated server", 0, 0);
	com_developer = CVar_Register("com_developer", "0", CVAR_BOOL, 0, "Developer mode", 0, 0);
	com_logFile = CVar_Register("com_logFile", "0", CVAR_INTEGER, 0, "Log console messages (1 = write, 2 = unbuffered write, 3 = append, 4 = unbuffered append)", 0, 4);

	// Add commands
	Cmd_AddCommand("showConsole", Com_ShowConsole_f);
	Cmd_AddCommand("quit", Com_Quit_f);

	// Initialize network
	NET_Init();
	Netchan_Init();

	// Initialize server and client
	SV_Init();
	CL_Init2();   // FIXME: Replace this later with CL_Init

	// Show or hide the system console
	if (com_dedicated->integerValue)
		Win_ShowConsole(true);
	else
		Win_ShowConsole(false);

	// Add the late commands
	Com_AddLateCommands();

	// Process commands
	Cbuf_Execute();

	com_initialized = true;

	Com_Printf("======== " ENGINE_NAME " Initialization Complete ========\n");
}

/*
 ==================
 Com_Shutdown
 ==================
*/
void Com_Shutdown ()
{
	static bool   isDown;

	if (setjmp(com_abortFrame))
		return;   // An error occurred, exit the entire frame

	// Avoid recursive shutdown
	if (isDown)
		return;
	isDown = true;

	// Remove commands
	Cmd_RemoveCommand("showConsole");
	Cmd_RemoveCommand("quit");

	// Shutdown server and client
	SV_Shutdown("Server quit", false);
	CL_Shutdown();

	// Shutdown networking
	NET_Shutdown();

	// Shutdown collision system
	//CM_Shutdown();

	// Shutdown file system
	FS_Shutdown();

	// Shutdown the subsystems that handle commands and variables
	Key_Shutdown();

	CVar_Shutdown();
	//Cmd_Shutdown();

	// Shutdown system module
	Win_ShutdownSystem();

	// Shutdown memory manager
	Mem_Shutdown();

	// Close log file
	Com_CloseLogFile();
}